package org.flashamaphone.controller
{
	import flash.events.TimerEvent;
	import flash.media.Sound;
	import flash.utils.ByteArray;
	import flash.utils.Timer;
	import flash.utils.getQualifiedSuperclassName;
	import flash.utils.getTimer;
	
	import org.flashamaphone.buffer.IBuffer;
	import org.flashamaphone.debug.Debugger;
	import org.flashamaphone.tones.ITone;
	import org.flashamaphone.tones.Note;
	import org.flashamaphone.tones.Tone;

	/**
	 * Keyboard Controller Board allows interaction with notes like a keyboard
	 * Notes can be pressed and released
	 */
	public class KeyboardControllerBoard implements ISoundController
	{	
		/** keys pressed */
		protected var keysPressed:Vector.<ITone> = new Vector.<ITone>();
		
		/** keys released still in release phase */
		protected var keysReleased:Vector.<ITone> = new Vector.<ITone>();
		
		/** sound buffer */
		protected var _soundbuffer:IBuffer;
		
		/** debugger */
		protected var _debug:Boolean = false;

		/**
		 * sound buffer setter
		 * 
		 * @param value
		 */
		public function set soundBuffer(value:*):void {
			_soundbuffer = value;
		}
		
		/**
		 * debug getter
		 * 
		 * @return debug
		 */
		public function get debug():Boolean {
			return _debug;
		}
		
		/**
		 * debug setter
		 * 
		 * @param value
		 */
		public function set debug(value:Boolean):void {
			_debug = value;
		}
		
		
		/** constructor */
		public function KeyboardControllerBoard(){}
		
		/**
		 * get pressed keys
		 * 
		 * @return tone vector
		 */
		public function get pressed():Vector.<ITone> {
			return keysPressed;
		}
		
		/**
		 * replace keys pressed - release all keys, press new ones 
		 * except for ones that were pressed before and are pressed still (keep those pressed)
		 * 
		 * @param pressed keys
		 */
		public function set pressed(newkeys:Vector.<ITone>):void {
			var currentlyPressed:Vector.<ITone> = new Vector.<ITone>();
			for each ( var key:ITone in keysPressed) {
				currentlyPressed.push(key);
			}
	
			// press new keys
			for ( var c:int = 0; c < newkeys.length; c++ ) {
				var indx:int = indexOfSimilarKeyPressed(newkeys[c]);
				if (indx != -1) {
					newkeys.splice(c, 1);
					currentlyPressed.splice(indx,1);
				} else {
					pressKey(newkeys[c]);
				}
			}
			
			// release remaining keys
			for each ( var pressed:ITone in currentlyPressed) {
				releaseKey(pressed);
			}
		}
		
		/**
		 * get if key is pressed
		 * 
		 * @param key
		 * @return if pressed
		 */
		public function isSimilarKeyPressed(value:ITone):Boolean {
			for each ( var t:ITone in keysPressed ) {
				if (t.frequency == value.frequency) {
					return true;
				}
			}
			return false;
		}
		
		/**
		 * get index of key pressed
		 * 
		 * @param key
		 * @return index of in vector
		 */
		public function indexOfSimilarKeyPressed(value:ITone):int {
			for ( var c:int = 0; c < keysPressed.length; c++ ) {
				if (keysPressed[c].frequency == value.frequency) {
					return c;
				}
			}
			return -1;
		}
		
		/**
		 * on key press
		 * 
		 * @param note
		 */
		public function pressKey(note:ITone):void {
			note.resetByteStream();
			keysPressed.push(note);
			if (!_soundbuffer.active) {
				_soundbuffer.activateBuffer(this);
			}
		}
		
		/**
		 * on key release
		 * 
		 * @param note
		 */
		public function releaseKey(note:ITone):void {
			var indx:int = keysPressed.indexOf(note);
			if (indx != -1) {
				keysPressed.splice(indx, 1);
				note.phase = Tone.UNPRESS_PHASE;
				keysReleased.push(note);
			}
		}
		
		/**
		 * release all keys
		 */
		public function releaseAllKeys():void {
			for each ( var k:ITone in keysPressed ) {
				releaseKey(k);
			}
		}
		
		/**
		 * on key release
		 * 
		 * @param note
		 */
		public function releaseSimilarKey(note:ITone):void {
			var indx:int = this.indexOfSimilarKeyPressed(note);
			if (indx != -1) {
				keysPressed.splice(indx, 1);
				note.phase = Tone.UNPRESS_PHASE;
				keysReleased.push(note);
			}
		}
		
		/**
		 * pull bytes from pressed notes
		 * 
		 * @param number of stereo samples to pull
		 * @return bytes pulled
		 */
		public function pull(numStereoSamples:int):ByteArray {
			var keys:Vector.<ITone> = new Vector.<ITone>();
			var key:ITone;
			
			// accumulate all keys pressed....
			for each (key in keysPressed) {
				keys.push(key);	
			}
			// ... with all keys released
			for each (key in keysReleased) {
				keys.push(key);	
			}
			
			// mix the signals
			var returnBytes:ByteArray
			//returnBytes = mix(keys, numStereoSamples);
			if ( keys.length == 0 || keys.length > 1) {
				// mixing is processor intensive, gotta do something about this soon
				returnBytes = mix(keys, numStereoSamples);
			} else {
				returnBytes = keys[0].stream(numStereoSamples*8);
				// remove key if no bytes are left - ie release is over
				if (returnBytes.length < numStereoSamples*8) {
					keysReleased.splice( keysReleased.indexOf(keys[0]), 1);
				}
			}
			if (debug) {
				Debugger.debug(returnBytes);
			}
			
			return returnBytes;			
		}
		
		/**
		 * mix all keys pressed
		 * 
		 * @param keys (ITone vector)
		 * @param number of samples
		 * @return mixed bytearray
		 */
		protected function mix(keys:Vector.<ITone>, numSamples:int):ByteArray {
			var numBytes:int = numSamples*8;
			var bytesList:Vector.<ByteArray> = new Vector.<ByteArray>();
			var volumes:Array = [];
			// get bytes for all keys for the number of samples desired
			for each (var key:ITone in keys) {
				var sound:ByteArray = key.stream(numBytes);
				
				// remove key if no bytes are left - ie release is over
				if (sound.length < numBytes) {
					keysReleased.splice( keysReleased.indexOf(key), 1);
				}
				
				sound.position = 0;
				bytesList.push(sound);
				volumes.push(key.voice.volume);
			}
			
			// break out each sample, add them up, and re-write byte array
			var mix:ByteArray = new ByteArray();
			for (var c:int = 0; c < numSamples; c++) {
				var combinedSample:Number = 0;
				var samplesCounted:Number = 0;
				for (var d:int = 0; d < bytesList.length; d++) {
					var bytes:ByteArray = bytesList[d];
					if (bytes.bytesAvailable >= 8) {
						var left:Number = bytes.readFloat(); // left
						var right:Number = bytes.readFloat(); // right
						combinedSample += (left + right)/2;
						samplesCounted += volumes[d];
					}
				}
				
				if (samplesCounted > 0) {
					mix.writeFloat(combinedSample/(samplesCounted)); // left
 					mix.writeFloat(combinedSample/(samplesCounted)); // right
				}
			}
			return mix;
		}
	}
}